3.3.8 尾调用优化

尾调用优化 Tail Call Optimisation

就是指某个函数的最后一步是调用另一个函数。

function f(x){
  return g(x);
}
1
2
3

是调用函数之后,还有别的操作,都不属于尾调用

尾调用不一定出现在函数尾部,只要是最后一步操作即可。

尾调用优化

函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。
如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。
等到B运行结束,将结果返回到A,B的调用记录才会消失。
如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。
所有的调用记录,就形成一个"调用栈"(call stack)。

"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。
如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。
这就是"尾调用优化"的意义。

尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。
但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。

柯里化(currying),意思是将多参数的函数转换成单参数的形式。这里也可以使用柯里化。

对于其他支持"尾调用优化"的语言(比如Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。

参考